home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / Tools / MotifApp / Extras / Callbacks.txt < prev    next >
Encoding:
Text File  |  1995-05-03  |  20.2 KB  |  687 lines

  1. A Callback Class
  2.  
  3.                                                                                                                                        Doug Young
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13. This paper briefly describes an alternate approach for using C++ member 
  14. functions with Xt callbacks. This material was originally written as an 
  15. appendix to Object-Oriented Programming with C++ and OSF/Motif f, 
  16. Prentice Hall, 1992, but was cut from the final manuscript in the interest of 
  17. simplicity and consistency. 
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25. The approach used throughout Object-Oriented Programming with C++ and OSF/Motif for dealing 
  26. with member functions and widget callbacks requires two functions. One function is declared as a 
  27. static member function and registered as a Motif callback using XtAddCallback(). An object's 
  28. this pointer is provided as client data when the callback is registered. When this static member 
  29. function is called, it retrieves the instance pointer from its client data argument and calls the second 
  30. function, which can be a normal member function, relative to that object. 
  31.  
  32. This approach leads to many member functions that are very similar. For example, almost every 
  33. callback function looks like:
  34.  
  35. void SomeClass::memberFunctionCallback ( Widget, 
  36.  
  37.                                            XtPointer clientData, 
  38.  
  39.                                            XtPointer )
  40.  
  41.   {
  42.  
  43.       SomeClass *obj = (SomeClass *) clientData;
  44.  
  45.       obj->memberFunction();
  46.  
  47.   }
  48.  
  49.  
  50.  
  51. The only difference between various static functions used as callbacks is the class to which the 
  52. function belongs, and the exact member function being called. The form is identical in all cases
  53.  
  54. Because the form of all these functions is so similar, it is possible to hide some of the details of 
  55. this approach. One way to do this is to capture the entire mechanism in a C++ class. This paper 
  56. examines an implementation of such a class, the Callback class.
  57.  
  58. The Callback class has some similarities to the Cmd class described in Chapter 7 of Object-
  59. Oriented Programming with C++ and OSF/Motif. However, the implementation is a bit different. 
  60. Basically, every class that wants to use a Callback object must create a new derived class that 
  61. overrides an execute() member function declared by the Callback class. This member function 
  62. is called by a static member function, registered by the Callback base class as the actual callback 
  63. function. The execute() function is expected to call a normal member function. 
  64.  
  65. This scheme requires only a single static member function per class, and only a single 
  66. execute() member function. The technique allows normal member functions to be registered as 
  67. pseudo-callbacks. However, the approach adds one more level of indirection than the alternate 
  68. approach.
  69.  
  70. Let's start by looking at the declaration of the Callback class. The entire mechanism is imple-
  71. mented in a header file, using macros and inline functions. The file Callback.h is written as follows:
  72.  
  73. 1  ////////////////////////////////////////////////////////
  74.  
  75. 2  // Callback.h: A class that encapsulates callbacks
  76.  
  77. 3  ////////////////////////////////////////////////////////
  78.  
  79. 4  #ifndef CALLBACK_H
  80.  
  81. 5  #define CALLBACK_H
  82.  
  83. 6  
  84.  
  85. 7  #include <Xm/Xm.h>
  86.  
  87. 8  #include <generic.h>
  88.  
  89. 9  
  90.  
  91. 10  class Callback {
  92.  
  93. 11  
  94.  
  95. 12    protected:
  96.  
  97. 13  
  98.  
  99. 14      Widget    _w;          // Widget for which callback is registered
  100.  
  101. 15      String    _name;       // The callback name, 
  102.  
  103. 16                             // needed to remove callback
  104.  
  105. 17      XtPointer _clientData; // Xt's idea of client data is used
  106.  
  107. 18                             // for the this pointer,
  108.  
  109. 19                             // so replace it with our own.
  110.  
  111. 20   
  112.  
  113. 21      // Constructor registers callback and saves other info
  114.  
  115. 22      // that might be needed later as data members
  116.  
  117. 23  
  118.  
  119. 24      Callback ( Widget w, String name, XtPointer clientData )
  120.  
  121. 25      { 
  122.  
  123. 26              XtAddCallback ( w, 
  124.  
  125. 27                                  name, 
  126.  
  127. 28                                  &Callback::callbackStub, 
  128.  
  129. 29                                  (XtPointer) this );
  130.  
  131. 30              _w          = w;
  132.  
  133. 31              _name       = name;
  134.  
  135. 32              _clientData = clientData;
  136.  
  137. 33      }
  138.  
  139. 34  
  140.  
  141. 35      // Destructor removes the callback, using save data members
  142.  
  143. 36  
  144.  
  145. 37      ~Callback()
  146.  
  147. 38      { 
  148.  
  149. 39          XtRemoveCallback ( _w, 
  150.  
  151. 40                             _name, 
  152.  
  153. 41                             &Callback::callbackStub, 
  154.  
  155. 42                             (XtPointer)this );
  156.  
  157. 43      }
  158.  
  159. 44   
  160.  
  161. 45      // Execute must be overriden by derived classes
  162.  
  163. 46  
  164.  
  165. 47      virtual void execute ( Widget, XtPointer, XtPointer ) = 0;
  166.  
  167. 48  
  168.  
  169. 49    private:
  170.  
  171. 50  
  172.  
  173. 51      // Generic callback function works for all callbacks
  174.  
  175. 52   
  176.  
  177. 53      static void callbackStub ( Widget    w, 
  178.  
  179. 54                                 XtPointer clientData, 
  180.  
  181. 55                                 XtPointer callData )
  182.  
  183. 56      {
  184.  
  185. 57              Callback *obj = (Callback *) clientData;
  186.  
  187. 58              obj->execute ( w, obj->_clientData, callData );
  188.  
  189. 59      }
  190.  
  191. 60  };
  192.  
  193. The Callback class declares three protected data members, the widget with which the callback 
  194. is registered, the name of the callback, and a pointer that can be used to point to some arbitrary data. 
  195. Recall that the static member function scheme uses the client data supported by 
  196. XtAddCallback() to pass the this pointer to the callback function. Although this scheme still 
  197. needs to have the this pointer, wrapping the callback in a class hides the process and provides a 
  198. place to keep some additional data on a per callback basis. This allows us to make the client data 
  199. argument available to programmers again.
  200.  
  201. The Callback constructor expects a widget, the name of a callback and a pointer to any client 
  202. data. It registers callbackStub(), which is a static member function defined by the Callback 
  203. class, as the named callback function for the given widget. The Callback constructor also retains the 
  204. widget, the name, and the client data as data members in the Callback object.
  205.  
  206. The destructor is very simple. It uses the callback name and widget kept in the object and calls 
  207. XtRemoveCallback() to remove the callback registered in the constructor.
  208.  
  209. The function callbackStub() is registered for all callbacks, regardless of the type. It is 
  210. declared as an inline static member function, and looks much like the other callback function used 
  211. throughout this book. It retrieves the this pointer provided as client data when the callback was 
  212. registered, casts it to the appropriate type, and calls the execute() member function for this 
  213. instance. Because all callbacks use this same member function, the type of the object passed as client 
  214. data will always be Callback. This makes this approach slightly more type safe than the alternate 
  215. approach, in spite of the cast, which is still needed.
  216.  
  217. The execute() member function is declared as a pure virtual function, and must therefore be 
  218. defined by derived classes. This function calls the member function registered as the pseudo-
  219. callback.
  220.  
  221. Now lets' take a first look at how this class is used. The Callback class cannot be used directly. 
  222. A derived class must be created from every class that needs to register callbacks. It is easiest to just 
  223. look at an example. The following ControlCallback class could be used with the Control class 
  224. described in Chapter 2 of Object-Oriented Programming with C++ and OSF/Motif. We could 
  225. declare the ControlCallback class as follows:
  226.  
  227. /////////////////////////////////////////////////////////////
  228.  
  229.   // ControlCallback.h: Callback class to be used with Control
  230.  
  231.   //////////////////////////////////////////////////////////////
  232.  
  233.       #ifndef CONTROLCALLBACK_H
  234.  
  235.       #define CONTROLCALLBACK_H
  236.  
  237.   
  238.  
  239.       #include "Callback.h"
  240.  
  241.   
  242.  
  243.       // Define ControllPMF as a type
  244.  
  245.   
  246.  
  247.   typedef void ( Control::*ControlPMF  )( Widget, XtPointer, XtPointer ); 
  248.  
  249.   
  250.  
  251.   // Declare the ControlCallback class as a derived calss of Callback
  252.  
  253.   
  254.  
  255.   class  ControlCallback : public Callback {
  256.  
  257.   
  258.  
  259.     public:    
  260.  
  261.   
  262.  
  263.      // Constructor takes a pointer to an instance of Control,
  264.  
  265.      // and a pointer to a Control member function, in addition
  266.  
  267.      // to the arguments requires by the Callback constructor
  268.  
  269.   
  270.  
  271.       ControlCallback  ( Control    *obj,  
  272.  
  273.                          ControlPMF  pmf, 
  274.  
  275.                          Widget      w, 
  276.  
  277.                          String      callbackName, 
  278.  
  279.                          XtPointer   clientData ) :
  280.  
  281.                                 Callback ( w, callbackName, clientData )
  282.  
  283.       { 
  284.  
  285.           _obj = obj; // Keep the instance pointer around
  286.  
  287.           _pmf = pmf; // Remember the member function pointer
  288.  
  289.       }
  290.  
  291.   
  292.  
  293.     protected:    
  294.  
  295.   
  296.  
  297.       Control     *_obj;  // Stores a Control object
  298.  
  299.           ControlPMF   _pmf;      // A pointer to a Control member function
  300.  
  301.   
  302.  
  303.     private:    
  304.  
  305.   
  306.  
  307.       // execute() is called from Callback class, indirectly
  308.  
  309.       // when callback is invoked. Use the stored Control instance
  310.  
  311.       // to call the member function. Pass the widget, calldata
  312.  
  313.       // and client data, as stored in this object.
  314.  
  315.   
  316.  
  317.       virtual void execute ( Widget w, 
  318.  
  319.                              XtPointer clientData,
  320.  
  321.                              XtPointer callData )
  322.  
  323.       {
  324.  
  325.            ( _obj->*_pmf )( w, clientData, callData ); 
  326.  
  327.        } 
  328.  
  329.   };
  330.  
  331.     
  332.  
  333. To use this class, we need to instantiate a ControlCallback object with the appropriate arguments 
  334. for each desired callback. Notice that only one ControlCallback class is needed regardless of how 
  335. many widgets or how many different member functions are supported by the Control class. 
  336.  
  337. However, notice that this ControlCallback class can only be used with the Control class. Using 
  338. this technique with other classes would require a new derived class, with the appropriate members 
  339. defined to point to instances of each new class. 
  340.  
  341. This seems like a lot of work, just to avoid writing a simple static member function. Fortunately, 
  342. we can use a simple technique to reduce the amount of work require down to a single line of code. 
  343. Let's return to the file Callback.h and look at the following macro, which is defined following the 
  344. Callback class declaration:
  345.  
  346. 61   #define Callbackdeclare(CLASS)                                                       \
  347.  
  348. 62   class CLASS;                                                                                 \
  349.  
  350. 63                                                                \
  351.  
  352. 64   typedef void (CLASS::*name2(CLASS,PMF))( Widget,             \
  353.  
  354. 65                                            XtPointer,          \
  355.  
  356. 66                                            XtPointer );        \
  357.  
  358. 67                                                                                                    \
  359.  
  360. 68   class name2(CLASS,Callback) : public Callback {                              \
  361.  
  362. 69                                                                \
  363.  
  364. 70    public:                                                                                 \
  365.  
  366. 71                                                                \
  367.  
  368. 72          name2(CLASS,Callback)( CLASS* obj,                        \
  369.  
  370. 73                                         name2(CLASS,PMF) pmf,              \
  371.  
  372. 74                                         Widget w,                          \
  373.  
  374. 75                                         String callbackName,               \
  375.  
  376. 76                             XtPointer clientData ) :           \
  377.  
  378. 77                                        Callback ( w,                               \
  379.  
  380. 78                                                       callbackName,                    \
  381.  
  382. 79                                                       clientData )                     \
  383.  
  384. 80      {                                                         \
  385.  
  386. 81      _obj = obj;                                                \
  387.  
  388. 82      _pmf = pmf;                                                \
  389.  
  390. 83   }                                                             \
  391.  
  392. 84                                                                 \
  393.  
  394. 85    protected:                                                                                   \
  395.  
  396. 86                                                                 \
  397.  
  398. 87          CLASS* _obj;                                                                   \
  399.  
  400. 88          name2(CLASS,PMF) _pmf;                                                         \
  401.  
  402. 89                                                                 \
  403.  
  404. 90    private:                                                                                     \
  405.  
  406. 91                                                                 \
  407.  
  408. 92          virtual     void execute ( Widget w,                           \
  409.  
  410. 93                                         XtPointer clientData,               \
  411.  
  412. 94                                         XtPointer callData )                \
  413.  
  414. 95      {                                                              \
  415.  
  416. 96              ( _obj->*_pmf )( w, clientData, callData );            \
  417.  
  418. 97      }                                                          \
  419.  
  420. 98      };                                                             \
  421.  
  422. 99                                                                 \
  423.  
  424. 100   name2(CLASS,Callback) *addCallback ( Widget w,               \
  425.  
  426. 101                                        String name,            \
  427.  
  428. 102                                        name2(CLASS,PMF) pmf,   \
  429.  
  430. 103                                                XtPointer clientData )  \
  431.  
  432. 104   {                                                            \
  433.  
  434. 105           name2(CLASS,Callback) *cb =                              \
  435.  
  436. 106                          new name2(CLASS,Callback)( this,          \
  437.  
  438. 107                                                 pmf,           \
  439.  
  440. 108                                                 w,             \
  441.  
  442. 109                                                 name,          \
  443.  
  444. 110                                                 clientData );  \
  445.  
  446. 111          return cb;                                                \
  447.  
  448. 112   }
  449.  
  450. 113  #endif
  451.  
  452. This macro captures the format of the ControlCallback class we just discussed. This macro uses 
  453. the name2() macro found in the file generic.h to concatenate two symbols together to generate one 
  454. new symbol. If we were to process the statement
  455.  
  456. CallbackDeclare(Control)
  457.  
  458. through the C++ preprocessor, the result would be approximately the same as the ControlCallback 
  459. class implemented earlier. There is one additional feature of this macro, the addCallback() 
  460. function defined starting on line 100. This is a convenience function that instantiates a Callback 
  461. object and provides the this pointer to the constructor. 
  462.  
  463. This macro is intended to be included in the declaration of a class. This makes the 
  464. addCallback() function a member function of the declaring class.
  465.  
  466. With this macro defined, it is very easy to use the Callback class. Let's look at how it could be 
  467. used with the Control class described in Chapter 2 of Object-Oriented Programming with C++ and 
  468. OSF/Motif. The class declaration is nearly identical to the declaration in that book, except that the 
  469. class has no static member functions, and the Callbackdeclare macro is included inside the 
  470. private portion of the class. Notice that the stop() and start() member functions support the 
  471. arguments expected by the Callback class as well.
  472.  
  473. 1   ////////////////////////////////////////////////////////////////////
  474.  
  475. 2   // Control.h: A start/stop pair of buttons for the stopwatch program
  476.  
  477. 3   ////////////////////////////////////////////////////////////////////
  478.  
  479. 4   #ifndef CONTROL_H
  480.  
  481. 5   #define CONTROL_H
  482.  
  483. 6   #include "BasicComponent.h"
  484.  
  485. 7   #include "Callback.h"
  486.  
  487. 8  
  488.  
  489. 9   class Timer;
  490.  
  491. 10  class Stopwatch;
  492.  
  493. 11  
  494.  
  495. 12  class Control : public BasicComponent {
  496.  
  497. 13  
  498.  
  499. 14    private:
  500.  
  501. 15  
  502.  
  503. 16      void start ( Widget, XtPointer, XtPointer );  
  504.  
  505. 17      void stop ( Widget, XtPointer, XtPointer ); 
  506.  
  507. 18  
  508.  
  509. 19      Callbackdeclare(Control)
  510.  
  511. 20  
  512.  
  513. 21   protected:
  514.  
  515. 22  
  516.  
  517. 23      Timer     *_timer;       // The timer controlled by this class
  518.  
  519. 24      Stopwatch *_stopwatch;   // The stopwatch containing this control
  520.  
  521. 25      Widget     _startWidget; // The start button
  522.  
  523. 26      Widget     _stopWidget;  // Stop button
  524.  
  525. 27  
  526.  
  527. 28    public:
  528.  
  529. 29  
  530.  
  531. 30      Control ( Widget, char * , Stopwatch *, Timer * );
  532.  
  533. 31  };
  534.  
  535. 32  #endif
  536.  
  537. The Control constructor is much the same as before. However, this version registers normal 
  538. member functions as callbacks using the addCallback() member function generated by the 
  539. Callbackdeclare() macro. Notice that the client data is specified as NULL in this case, and 
  540. that no this pointer is required.
  541.  
  542. 1  //////////////////////////////////////////////////////////////////
  543.  
  544. 2  // Control.C: A start/stop pair of buttons for the stopwatch program
  545.  
  546. 3  ///////////////////////////////////////////////////////////////////
  547.  
  548. 4  #include "Control.h"
  549.  
  550. 5  #include <Xm/Xm.h>
  551.  
  552. 6  #include <Xm/RowColumn.h>
  553.  
  554. 7  #include <Xm/PushB.h>
  555.  
  556. 8  #include "Timer.h"
  557.  
  558. 9  #include "Stopwatch.h"
  559.  
  560. 10  
  561.  
  562. 11  Control::Control ( Widget    parent, 
  563.  
  564. 12                     char      *name, 
  565.  
  566. 13                     Stopwatch *stopwatch,
  567.  
  568. 14                     Timer     *timer ) : BasicComponent ( name )
  569.  
  570. 15  {
  571.  
  572. 16  
  573.  
  574. 17      _timer = timer;  // Keep a pointer to the timer.
  575.  
  576. 18  
  577.  
  578. 19      _stopwatch = stopwatch;
  579.  
  580. 20  
  581.  
  582. 21      // Create the component's widget tree
  583.  
  584. 22  
  585.  
  586. 23      _w = XmCreateRowColumn ( parent, _name, NULL, 0 );
  587.  
  588. 24  
  589.  
  590. 25      _startWidget = XtCreateManagedWidget ( "start",
  591.  
  592. 26                                             xmPushButtonWidgetClass, 
  593.  
  594. 27                                             _w,  NULL, 0 );
  595.  
  596. 28      _stopWidget = XtCreateManagedWidget ( "stop", 
  597.  
  598. 29                                            xmPushButtonWidgetClass, 
  599.  
  600. 30                                            _w, NULL, 0 );
  601.  
  602. 31  
  603.  
  604. 32      // Register callbacks, specifying the object's instance 
  605.  
  606. 33      // pointer as client data.
  607.  
  608. 34  
  609.  
  610. 35      addCallback ( _startWidget, 
  611.  
  612. 36                            XmNactivateCallback, 
  613.  
  614. 37                            &Control::start,
  615.  
  616. 38                            NULL );
  617.  
  618. 39  
  619.  
  620. 40      addCallback ( _stopWidget, 
  621.  
  622. 41                            XmNactivateCallback, 
  623.  
  624. 42                            &Control::stopCallback,
  625.  
  626. 43                            NULL );
  627.  
  628. 44  
  629.  
  630. 45  }
  631.  
  632. The stop() and start() member functions are written the same as they were previously, 
  633. except that the functions take additional arguments, which are unused in this example.
  634.  
  635. 46  void Control::start( Widget, XtPointer, XtPointer )
  636.  
  637. 47  {
  638.  
  639. 48      _timer->start();
  640.  
  641. 49      _stopwatch->timerStarted();
  642.  
  643. 50  }
  644.  
  645. 51  void Control::stop ( Widget, XtPointer, XtPointer )
  646.  
  647. 52  {
  648.  
  649. 53      _timer->stop();
  650.  
  651. 54      _stopwatch->timerStopped();
  652.  
  653. 55  }
  654.  
  655. This approach offers several advantage. It is simpler to use, given the complete implementation 
  656. of the Callback class and the Callbackdeclare macro. It is also slightly more type-safe. The 
  657. only cast occurs in association with the clientData argument to the callbackStub() 
  658. function. However, this is known to be a Callback object, regardless of what derived class is actually 
  659. being used, making this cast almost completely safe.
  660.  
  661. Using the Callbackdeclare macro reduces the number of functions a programmer must 
  662. write and relieves the tedium of defining two functions for every callback. The total number of 
  663. functions is also reduced, since the same static member function is used for all callbacks registered 
  664. by a given class. Finally, some may find this technique more aesthetically appealing than registering 
  665. static member functions and dealing with the C functions directly.
  666.  
  667. Nothing comes for free, and there are a few disadvantages to this approach. First, the Callback 
  668. mechanism adds yet one more level of indirection, requiring an additional function call for each 
  669. callback. This is probably not objectionable in most cases.
  670.  
  671. A second problem could be more problematic in some cases. When a widget is destroyed, Xt 
  672. removes all callbacks from the widget's callback lists. When using this approach, Xt will also 
  673. remove the callbackStub() function registered with the widget. However, it cannot know to 
  674. delete the Callback object. If widgets are destroyed dynamically, the implementation described 
  675. above will continue to use memory even though the functions supported by the class will never be 
  676. called.
  677.  
  678. It is possible to work around this problem by registering an XmNdestroyCallback function 
  679. for each widget and each callback class, whose task it is to free the Callback class when the widget 
  680. is destroyed. However, this adds even more to the complexity and overhead of the underlying 
  681. mechanisms that implement this approach.
  682.  
  683. I would like to credit Anil Pal for suggesting this approach, and demonstrating an earlier 
  684. version, from which the Callback class evolved.
  685.  
  686.  
  687.